/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.data;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.SortedMap;
import java.util.TreeMap;

import android.net.Uri;
import android.provider.MediaStore;

import com.android.gallery3d.common.ApiHelper;

// MergeAlbum merges items from two or more MediaSets. It uses a Comparator to
// determine the order of items. The items are assumed to be sorted in the input
// media sets (with the same order that the Comparator uses).
//
// This only handles MediaItems, not SubMediaSets.
/**
 * 合并image和video数据集合
 * 
 * @author lewa
 *
 */
public class LocalMergeAlbum extends MediaSet implements ContentListener {
	private static final String TAG = LocalMergeAlbum.class.getSimpleName();
	private static final int PAGE_SIZE = 64;

	private final Comparator<MediaItem> mComparator;
	private final MediaSet[] mSources;

	private FetchCache[] mFetcher;
	private int mSupportedOperation;
	private int mBucketId;

	// mIndex maps global position to the position of each underlying media
	// sets.
	private TreeMap<Integer, int[]> mIndex = new TreeMap<Integer, int[]>();

	public LocalMergeAlbum(Path path, Comparator<MediaItem> comparator,
			MediaSet[] sources, int bucketId) {
		super(path, INVALID_DATA_VERSION);
		Log.d(TAG, "LocalMergeAlbum -- path: " + path.toString());
		mComparator = comparator;
		mSources = sources;
		mBucketId = bucketId;
		for (MediaSet set : mSources) {
			set.addContentListener(this);
		}
		reload();
	}

	@Override
	public boolean isCameraRoll() {
		if (mSources.length == 0)
			return false;
		for (MediaSet set : mSources) {
			if (!set.isCameraRoll())
				return false;
		}
		return true;
	}

	private void updateData() {
		Log.d(TAG, "updateData");
		ArrayList<MediaSet> matches = new ArrayList<MediaSet>();
		int supported = mSources.length == 0 ? 0 : MediaItem.SUPPORT_ALL;
		mFetcher = new FetchCache[mSources.length];
		for (int i = 0, n = mSources.length; i < n; ++i) {
			mFetcher[i] = new FetchCache(mSources[i]);
			supported &= mSources[i].getSupportedOperations();
		}
		mSupportedOperation = supported;
		mIndex.clear();
		mIndex.put(0, new int[mSources.length]);
	}

	private void invalidateCache() {
		Log.d(TAG, "invalidateCache");
		for (int i = 0, n = mSources.length; i < n; i++) {
			mFetcher[i].invalidate();
		}
		mIndex.clear();
		mIndex.put(0, new int[mSources.length]);
	}

	@Override
	public Uri getContentUri() {
		Log.d(TAG, "getContentUri");
		String bucketId = String.valueOf(mBucketId);
		if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) {
			return MediaStore.Files.getContentUri("external").buildUpon()
					.appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId)
					.build();
		} else {
			// We don't have a single URL for a merged image before ICS
			// So we used the image's URL as a substitute.
			return MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()
					.appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId)
					.build();
		}
	}

	@Override
	public String getName() {
		return mSources.length == 0 ? "" : mSources[0].getName();
	}

	@Override
	public int getMediaItemCount() {
		Log.d(TAG, "getMediaItemCount");
		return getTotalMediaItemCount();
	}

	@Override
	public ArrayList<MediaItem> getMediaItem(int start, int count) {
		Log.d(TAG, "getMediaItem -- start: " + start + " count: " + count);
		// First find the nearest mark position <= start.
		SortedMap<Integer, int[]> head = mIndex.headMap(start + 1);
		int markPos = head.lastKey();
		int[] subPos = head.get(markPos).clone();
		MediaItem[] slot = new MediaItem[mSources.length];

		int size = mSources.length;

		// fill all slots
		for (int i = 0; i < size; i++) {
			slot[i] = mFetcher[i].getItem(subPos[i]);
		}

		ArrayList<MediaItem> result = new ArrayList<MediaItem>();

		for (int i = markPos; i < start + count; i++) {
			int k = -1; // k points to the best slot up to now.
			for (int j = 0; j < size; j++) {
				if (slot[j] != null) {
					if (k == -1 || mComparator.compare(slot[j], slot[k]) < 0) {
						k = j;
					}
				}
			}

			// If we don't have anything, all streams are exhausted.
			if (k == -1)
				break;

			// Pick the best slot and refill it.
			subPos[k]++;
			if (i >= start) {
				result.add(slot[k]);
			}
			slot[k] = mFetcher[k].getItem(subPos[k]);

			// Periodically leave a mark in the index, so we can come back
			// later.
			if ((i + 1) % PAGE_SIZE == 0) {
				mIndex.put(i + 1, subPos.clone());
			}
		}

		return result;
	}

	@Override
	public int getTotalMediaItemCount() {
		Log.d(TAG, "getTotalMediaItemCount");
		int count = 0;
		for (MediaSet set : mSources) {
			count += set.getTotalMediaItemCount();
		}
		return count;
	}

	@Override
	public long reload() {
		Log.d(TAG, "reload");
		boolean changed = false;
		for (int i = 0, n = mSources.length; i < n; ++i) {
			if (mSources[i].reload() > mDataVersion)
				changed = true;
		}
		if (changed) {
			mDataVersion = nextVersionNumber();
			updateData();
			invalidateCache();
		}
		return mDataVersion;
	}

	@Override
	public void onContentDirty() {
		Log.d(TAG, "onContentDirty");
		notifyContentChanged();
	}

	@Override
	public int getSupportedOperations() {
		Log.d(TAG, "getSupportedOperations");
		return mSupportedOperation;
	}

	@Override
	public void delete() {
		for (MediaSet set : mSources) {
			set.delete();
		}
	}

	@Override
	public void rotate(int degrees) {
		for (MediaSet set : mSources) {
			set.rotate(degrees);
		}
	}

	private static class FetchCache {
		private MediaSet mBaseSet;
		private SoftReference<ArrayList<MediaItem>> mCacheRef;
		private int mStartPos;

		public FetchCache(MediaSet baseSet) {
			mBaseSet = baseSet;
		}

		public void invalidate() {
			mCacheRef = null;
		}

		public MediaItem getItem(int index) {
			Log.d(TAG, "FetchCache -- getItem index: " + index);
			boolean needLoading = false;
			ArrayList<MediaItem> cache = null;
			if (mCacheRef == null || index < mStartPos
					|| index >= mStartPos + PAGE_SIZE) {
				needLoading = true;
			} else {
				cache = mCacheRef.get();
				if (cache == null) {
					needLoading = true;
				}
			}

			if (needLoading) {
				cache = mBaseSet.getMediaItem(index, PAGE_SIZE);
				mCacheRef = new SoftReference<ArrayList<MediaItem>>(cache);
				mStartPos = index;
			}

			if (index < mStartPos || index >= mStartPos + cache.size()) {
				return null;
			}

			return cache.get(index - mStartPos);
		}
	}

	@Override
	public boolean isLeafAlbum() {
		return true;
	}
}
